package com.hsyt.pms.org.dubbo.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hsyt.pfc.support.util.LoggerUtil;
import com.hsyt.pms.api.dto.org.OrgTypeQueryDto;
import com.hsyt.pms.api.entity.org.Org;
import com.hsyt.pms.api.entity.org.OrgES;
import com.hsyt.pms.api.vo.org.OrgTypeVo;
import com.hsyt.pms.orm.org.OrgDao;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.action.bulk.BulkProcessor;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Description: 机构引擎核心控制实现类
 * @ClassName: OrgGridSearcher
 * @Package: com.hsyt.pms.org.dubbo.impl
 * @Author: wangze@hsyuntai.com
 * @Date: 2019/02/15 9:26
 * @Copyright: 版权归 HSYUNTAI 所有
 */
@Slf4j
@Component("orgGridSearcher")
public class OrgGridSearcher extends AbstractSearcher {

    private static final Logger LOGGER = LoggerFactory.getLogger(OrgGridSearcher.class);

    @Resource
    private OrgDao orgDao;

    @Override
    protected SearchRequestBuilder getSearchRequestBuilder(TransportClient esClient, Org searchDto) {
        String[] fields = {"id", "OrgCode", "OrgName", "OrgThumbnail", "OrgAddress", "longitude", "latitude", "OrgCityCode", "sortNum"};
        SearchRequestBuilder requestBuilder = null;
        BoolQueryBuilder bq = QueryBuilders.boolQuery();
        BoolQueryBuilder bq2 = QueryBuilders.boolQuery();
        bq2.should(QueryBuilders.termQuery("OrgCityCode", searchDto.getCityCode()));
        bq.must(bq2);
        bq.should(QueryBuilders.matchQuery(searchDto.getName(), "OrgName"));
        requestBuilder = esClient.prepareSearch("org")
                .setTypes("org")
                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .setQuery(bq)
                .setFetchSource(fields, null)
                .addSort("sortNum", SortOrder.ASC)
                .addSort("id", SortOrder.ASC)
                .setFrom((searchDto.getStartPage() - 1) * searchDto.getPageSize()).setSize(searchDto.getPageSize());
        return requestBuilder;
    }

    protected SearchRequestBuilder getSearchRequestBuilderNew(TransportClient esClient, Org searchDto) {
        String[] fields = {"OrgName", "OrgCode", "OrgThumbnail", "OrgAddress", "OrgCityCode", "location", "sortNum", "id"};
        SearchRequestBuilder requestBuilder = null;
        BoolQueryBuilder bq = QueryBuilders.boolQuery();
        BoolQueryBuilder bq2 = QueryBuilders.boolQuery();
        bq2.should(QueryBuilders.termQuery("OrgCityCode", searchDto.getCityCode()));
        bq.must(bq2);
        bq.should(QueryBuilders.matchPhraseQuery(searchDto.getName(), "OrgName"));
        requestBuilder = esClient.prepareSearch("org").setTypes("org").setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .setQuery(bq)
                .setFetchSource(fields, null)
                .addSort("sortNum", SortOrder.ASC)
                .addSort(SortBuilders.geoDistanceSort("location", Double.valueOf(searchDto.getLatitude()), Double.valueOf(searchDto.getLongitude())).unit(DistanceUnit.METERS).order(SortOrder.ASC).point(Double.valueOf(searchDto.getLatitude()), Double.valueOf(searchDto.getLongitude())))
                .addSort("id", SortOrder.ASC)
                .setFrom((searchDto.getStartPage() - 1) * searchDto.getPageSize()).setSize(searchDto.getPageSize());
        return requestBuilder;
    }

    @Override
    protected HighlightBuilder getHighlightBuilder() {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("OrgName").forceSource(true).preTags("^").postTags("^");
        return highlightBuilder;
    }

    public Integer createOrgIndex(TransportClient esClient, Settings settings) {
        BulkProcessor bulkProcessor = genBulkProcessor(esClient);
        XContentBuilder mappingBuilder = null;
        try {
            mappingBuilder = XContentFactory.jsonBuilder()
                    .startObject()
                    .startObject("org")
                    .startObject("properties")
                    .startObject("id").field("type", "text").field("fielddata", "true").field("store", "true").endObject()
                    .startObject("OrgCode").field("type", "text").field("store", "true").endObject()
                    .startObject("OrgName").field("type", "text").field("store", "true").endObject()
                    .startObject("OrgThumbnail").field("type", "text").field("store", "true").endObject()
                    .startObject("OrgAddress").field("type", "text").field("store", "true").endObject()
                    .startObject("longitude").field("type", "text").field("store", "true").endObject()
                    .startObject("latitude").field("type", "text").field("store", "true").endObject()
                    .startObject("OrgCityCode").field("type", "text").field("store", "true").endObject()
                    .startObject("location").field("type", "geo_point").endObject()
                    .startObject("sortNum").field("type", "text").field("fielddata", "true").field("store", "true").endObject()
                    .endObject()
                    .endObject()
                    .endObject();
        } catch (Exception e) {
            LoggerUtil.error(LOGGER, "createIndex 创建 mapping 失败 : {0}", e.getMessage());
        }
        IndicesAdminClient indicesAdminClient = esClient.admin().indices();
        indicesAdminClient.prepareCreate("org").setSettings(settings).addMapping("org", mappingBuilder).get();
        List<OrgTypeVo> list = orgDao.queryOrgTypeByDto(new OrgTypeQueryDto());
        try {
            for (OrgTypeVo org : list) {
                byte[] json = JSONObject.toJSONBytes(org);
                IndexRequest request = new IndexRequest("org", "org", org.getOrgCode()).source(json);
                bulkProcessor.add(request);
            }
            bulkProcessor.flush();
        } catch (Exception e) {
            LoggerUtil.error(LOGGER, "构建医院索引异常： {0}", e.getMessage());
        }
        return CollectionUtils.size(list);
    }

    /**
     * 创建索引(新增按经纬度排序)
     *
     * @param esClient
     */
    public Integer createOrgIndex(TransportClient esClient) {
        try {
            esClient.admin().indices().prepareCreate("org").execute().actionGet();
            PutMappingRequest putMapping = Requests.putMappingRequest("org").type("org").source(createMapping("org"));
            esClient.admin().indices().putMapping(putMapping).actionGet();
        } catch (Exception e) {
            LoggerUtil.error(LOGGER, "删除机构索引库失败： {0}", e.getMessage());
        }
        return addIndexData(esClient);
    }

    /**
     * 创建Mapping
     *
     * @param indexType
     * @return
     */
    public static XContentBuilder createMapping(String indexType) {
        XContentBuilder mapping = null;
        try {
            mapping = XContentFactory.jsonBuilder().startObject()
                    .startObject(indexType).startObject("properties")
                    .startObject("id").field("type", "text").field("fielddata", "true").endObject()
                    .startObject("name").field("type", "text").endObject()
                    .startObject("orgCode").field("type", "text").endObject()
                    .startObject("cityCode").field("type", "text").endObject()
                    .startObject("address").field("type", "text").endObject()
                    .startObject("sortNum").field("type", "text").field("fielddata", "true").endObject()
                    .endObject().endObject().endObject();
        } catch (Exception e) {
            LoggerUtil.error(LOGGER, "创建Mapping： {0}", e.getMessage());
        }
        return mapping;
    }

    /**
     * 封装索引数据
     *
     * @param esClient
     * @return
     */
    public Integer addIndexData(TransportClient esClient) {
        BulkRequestBuilder bulkRequest = null;
        try {
            bulkRequest = esClient.prepareBulk();
            List<OrgTypeVo> list = orgDao.queryOrgTypeByDto(new OrgTypeQueryDto());
            for (OrgTypeVo org : list) {
                IndexRequest request = esClient.prepareIndex("org", "org", org.getOrgCode()).setSource(obj2JsonData(org)).request();
                bulkRequest.add(request);
            }
            bulkRequest.execute().actionGet();
        } catch (Exception e) {
            LoggerUtil.error(LOGGER, "创建Mapping： {0}", e.getMessage());
        }
        return bulkRequest.numberOfActions();
    }

    /**
     * 封装为JSON数据
     *
     * @param Org
     * @return
     */
    public static String obj2JsonData(Org Org) {
        String jsonData = null;
        try {
            XContentBuilder jsonBuild = XContentFactory.jsonBuilder().startObject()
                    .field("id", Org.getId())
                    .field("OrgName", Org.getName())
                    .field("OrgCode", Org.getOrgCode())
                    .field("sortNum", Org.getSortNum())
                    .startArray("location").value(Double.valueOf(Org.getLongitude())).value(Double.valueOf(Org.getLatitude())).endArray()
                    .endObject();
            jsonData = jsonBuild.string();
        } catch (Exception e) {
        }
        return jsonData;
    }

    /**
     * 封装为JSON数据
     *
     * @param Org
     * @return
     */
    public static String obj2JsonData(OrgTypeVo Org) {
        String jsonData = null;
        try {
            XContentBuilder jsonBuild = XContentFactory.jsonBuilder().startObject()
                    .field("id", Org.getId())
                    .field("name", Org.getName())
                    .field("orgCode", Org.getOrgCode())
                    .field("sortNum", Org.getSortNum())
                    .endObject();
            jsonData = jsonBuild.string();
        } catch (Exception e) {
        }
        return jsonData;
    }


    /**
     * 测试类：  创建搜索引擎服务 -> 创建索引 -> 搜索索引
     */
    public static void main(String[] args) {
        TransportClient transportClient = null;
        try {
            transportClient = new PreBuiltTransportClient(Settings.builder().put("cluster.name", "yt-pop-fd-index").put("client.transport.sniff", true).build());
            transportClient.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
//        commonTest(transportClient); // elastic搜索引擎简单测试
        latLonTest(transportClient); //elastic搜索引擎经纬度距离测试
    }

    /**
     * elastic搜索引擎简单测试
     *
     * @param transportClient
     */
    public static void commonTest(TransportClient transportClient) {
        Settings settings = Settings.builder().put("index.number_of_shards", 5).put("index.number_of_replicas", 1).build();
        // 创建索引
//        createOrgIndexTest(transportClient, settings);
        // 开始搜索
        Org searchDto = new Org();
        searchDto.setCityCode("330100");
        searchDto.setName("王泽");
        System.out.println(JSON.toJSONString(searchIndex(transportClient, searchDto)));
    }

    /**
     * elastic搜索引擎经纬度距离测试
     *
     * @param transportClient
     */
    public static void latLonTest(TransportClient transportClient) {
//        createIndex(transportClient);
//        addIndexDataTest(transportClient);
        double lat = 39.929986;
        double lon = 116.395645;
        orderbyDistance(transportClient, lat, lon);
    }

    /**
     * 创建索引
     *
     * @param esClient
     * @param settings
     */
    public static void createOrgIndexTest(TransportClient esClient, Settings settings) {
        try {
            esClient.admin().indices().prepareDelete("org").execute().actionGet();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        BulkProcessor bulkProcessor = genBulkProcessorTest(esClient);
        XContentBuilder mappingBuilder = null;
        try {
            mappingBuilder = XContentFactory.jsonBuilder()
                    .startObject()
                    .startObject("org")
                    .startObject("properties")
                    .startObject("id").field("type", "text").field("fielddata", "true").field("store", "true").endObject()
                    .startObject("OrgCityCode").field("type", "text").field("store", "true").endObject()
                    .startObject("OrgName").field("type", "text").field("store", "true").endObject()
                    .endObject()
                    .endObject()
                    .endObject();
        } catch (Exception e) {
            System.out.println("createIndex 创建 mapping 失败");
        }
        IndicesAdminClient indicesAdminClient = esClient.admin().indices();
        indicesAdminClient.prepareCreate("org").setSettings(settings).addMapping("org", mappingBuilder).get();
        List<Org> list1 = new ArrayList<Org>();
        Org Org1 = new Org();
        Org1.setId(Long.valueOf("1111"));
        Org1.setName("王泽");
        Org1.setOrgCode("330100");
        list1.add(Org1);
        Org Org2 = new Org();
        Org2.setId(Long.valueOf("2222"));
        Org2.setName("张泽");
        Org2.setOrgCode("330100");
        list1.add(Org2);
        Org Org3 = new Org();
        Org3.setId(Long.valueOf("3333"));
        Org3.setName("毛泽");
        Org3.setOrgCode("330200");
        list1.add(Org3);
        try {
            for (Org Org : list1) {
                byte[] json = JSONObject.toJSONBytes(Org);
                IndexRequest request = new IndexRequest("org", "org", String.valueOf(Org.getId())).source(json);
                bulkProcessor.add(request);
            }
            bulkProcessor.flush();
            System.out.println("机构索引已构建完成, 一共建立" + list1.size() + "条索引");
        } catch (Exception e) {
            System.out.println("构建机构索引异常");
        }
    }

    /**
     * 搜索-DSL打印 + 高亮设置
     *
     * @param esClient
     * @return
     */
    public static List<Map<String, Object>> searchIndex(TransportClient esClient, Org searchDto) {
        SearchRequestBuilder requestBuilder = null;
        requestBuilder = getSearchRequestBuilderTest(esClient, searchDto);
        requestBuilder.highlighter(getHighlightBuilderTest());
        SearchResponse response = requestBuilder.get();
        System.out.println("搜索 DSL => {}" + requestBuilder.toString());
        long total = response.getHits().getTotalHits();
        if (total == 0) {
            return null;
        }
        List<Map<String, Object>> jsonList = new ArrayList<Map<String, Object>>();
        for (SearchHit hit : response.getHits().getHits()) {
            Map<String, Object> indexMap = hit.getSource();
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            for (Map.Entry<String, HighlightField> entry : highlightFields.entrySet()) {
                String text = (String) indexMap.get(entry.getKey());
                if (text != null) {
                    indexMap.put(entry.getKey(), text.replace(searchDto.getName(), "<font color=red>" + searchDto.getName() + "</font>"));
                }
            }
            jsonList.add(indexMap);
        }
        return jsonList;
    }

    /**
     * 查询设置（筛选，排序）
     *
     * @return
     */
    protected static SearchRequestBuilder getSearchRequestBuilderTest(TransportClient esClient, Org searchDto) {
        String[] fields = {"id", "OrgName", "OrgCityCode"};
        SearchRequestBuilder requestBuilder = null;
        BoolQueryBuilder bq = QueryBuilders.boolQuery();
        BoolQueryBuilder bq2 = QueryBuilders.boolQuery();
        bq2.should(QueryBuilders.termQuery("OrgCityCode", searchDto.getCityCode()));
        bq.must(bq2);
        bq.should(QueryBuilders.multiMatchQuery(searchDto.getName(), "OrgName"));
        requestBuilder = esClient.prepareSearch("org")
                .setTypes("org")
                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .setQuery(bq)
                .setFetchSource(fields, null)
                .addSort("id", SortOrder.DESC);
        return requestBuilder;
    }

    /**
     * 高亮字段设置
     *
     * @return
     */
    protected static HighlightBuilder getHighlightBuilderTest() {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("OrgName").preTags("^").postTags("^");
        return highlightBuilder;
    }


    /**
     * 距离排序-------------------------------------------------------
     *
     * @param esClient
     */


    // 创建索引
    public static void createIndex(TransportClient esClient) {
        try {
            esClient.admin().indices().prepareDelete("org").execute().actionGet();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        // 创建Mapping
        XContentBuilder mapping = createMappingTest("org");
        // 创建一个空索引
        esClient.admin().indices().prepareCreate("org").execute().actionGet();
        PutMappingRequest putMapping = Requests.putMappingRequest("org").type("org").source(mapping);
        PutMappingResponse response = esClient.admin().indices().putMapping(putMapping).actionGet();
        if (!response.isAcknowledged()) {
            System.out.println("Could not define mapping for type [" + "org" + "]/[" + "org" + "].");
        } else {
            System.out.println("Mapping definition for [" + "org" + "]/[" + "org" + "] succesfully created.");
        }
    }

    // 创建mapping
    public static XContentBuilder createMappingTest(String indexType) {
        XContentBuilder mapping = null;
        try {
            mapping = XContentFactory.jsonBuilder().startObject()
                    .startObject(indexType).startObject("properties")
                    .startObject("id").field("type", "text").field("fielddata", "true").endObject()
                    .startObject("OrgName").field("type", "text").endObject()
                    .startObject("location").field("type", "geo_point").endObject()
                    .startObject("OrgCode").field("type", "text").endObject()
                    .startObject("OrgCityCode").field("type", "text").endObject()
                    .startObject("OrgThumbnail").field("type", "text").endObject()
                    .startObject("OrgAddress").field("type", "text").endObject()
                    .startObject("sortNum").field("type", "text").field("fielddata", "true").endObject()
                    .endObject().endObject().endObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mapping;
    }

    // 添加数据
    public static Integer addIndexDataTest(TransportClient esClient) {
        List<String> OrgList = new ArrayList<String>();
        OrgES Org1 = new OrgES(2, "王泽", new BigDecimal("39.329986").doubleValue(), new BigDecimal("116.395645").doubleValue(), "111", "330100", "", "", 22);
        OrgList.add(obj2JsonData(Org1));
        OrgES Org2 = new OrgES(1, "张泽", new BigDecimal("39.329986").doubleValue(), new BigDecimal("116.395645").doubleValue(), "222", "330100", "", "", 22);
        OrgList.add(obj2JsonData(Org2));
        OrgES Org3 = new OrgES(3, "毛泽", new BigDecimal("39.929986").doubleValue(), new BigDecimal("116.995645").doubleValue(), "333", "330100", "", "", 11);
        OrgList.add(obj2JsonData(Org3));
        // 创建索引库
        List<IndexRequest> requests = new ArrayList<IndexRequest>();
        for (int i = 0; i < OrgList.size(); i++) {
            IndexRequest request = esClient.prepareIndex("org", "org").setSource(OrgList.get(i)).request();
            requests.add(request);
        }

        // 批量创建索引
        BulkRequestBuilder bulkRequest = esClient.prepareBulk();
        for (IndexRequest request : requests) {
            bulkRequest.add(request);
        }

        BulkResponse bulkResponse = bulkRequest.execute().actionGet();
        if (bulkResponse.hasFailures()) {
            System.out.println("批量创建索引错误！");
        }
        return bulkRequest.numberOfActions();
    }

    public static String obj2JsonData(OrgES user) {
        String jsonData = null;
        try {
            XContentBuilder jsonBuild = XContentFactory.jsonBuilder();
            jsonBuild.startObject()
                    .field("id", user.getId())
                    .field("OrgName", user.getOrgName())
                    .field("OrgCode", user.getOrgCode())
                    .field("OrgCityCode", user.getOrgCityCode())
                    .field("OrgThumbnail", user.getOrgThumbnail())
                    .field("OrgAddress", user.getOrgAddress())
                    .field("sortNum", user.getSortNum())
                    .startArray("location").value(user.getLon()).value(user.getLat()).endArray()
                    .endObject();
            jsonData = jsonBuild.string();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return jsonData;
    }

    public static void orderbyDistance(Client client, double lat, double lon) {
        String[] fields = {"id", "OrgName", "OrgCode", "OrgCityCode", "location", "OrgThumbnail", "OrgAddress", "sortNum"};
        SearchRequestBuilder srb = client.prepareSearch("org").setTypes("org").setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
                .setQuery(QueryBuilders.boolQuery().must(QueryBuilders.boolQuery().should(QueryBuilders.termQuery("OrgCityCode", "330100"))).should(QueryBuilders.matchPhraseQuery("王", "OrgName")))
                .setFetchSource(fields, null)
                .addSort("sortNum", SortOrder.ASC)
                .addSort(SortBuilders.geoDistanceSort("location", lat, lon).unit(DistanceUnit.METERS).order(SortOrder.ASC).point(lat, lon))
                .addSort("id", SortOrder.ASC)
                .setFrom(0).setSize(100);
        SearchResponse response = srb.execute().actionGet();
        for (SearchHit hit : response.getHits().getHits()) {
            BigDecimal geoDis = new BigDecimal((Double) hit.getSortValues()[1]);
            System.out.println((String) hit.getSource().get("OrgName") + "的坐标距离我" + geoDis.setScale(2, BigDecimal.ROUND_HALF_UP) + DistanceUnit.METERS.toString());
        }
    }

}
